// 不上传到cdn的打包文件备份
const path = require('path')
const fs = require('fs')
const gulp = require('gulp')
const plug = require('gulp-load-plugins')() // 自动加载插件,无需单个引用
const browserSync = require('browser-sync')
const { createProxyMiddleware } = require('http-proxy-middleware')
const packageJson = require('./package.json')

// 环境配置
const ENV = process.env.ENV_CONFIG
const ENV_CONFIG = packageJson.envConfig[ENV]

// 文件路径
const src = {
  revCollector: 'rev-collector',
  library: ['src/library/*.min.js', 'src/library/*.min.css'],
  image: ['src/assets/images/**/*.*', '!src/assets/images/private/**/*.*', `src/assets/images/private/${ENV_CONFIG.PRIVATE}/**/*.*`],
  style: ['src/assets/styles/**/*.less', '!src/assets/styles/private/**/*.less', `src/assets/styles/private/${ENV_CONFIG.PRIVATE}/**/*.less`],
  json: ['src/assets/jsons/**/*.json', '!src/assets/jsons/private/**/*.json', `src/assets/jsons/private/${ENV_CONFIG.PRIVATE}/**/*.json`],
  jsPublic: 'src/assets/scripts/public/**/*.js', // 合并成一个文件编译
  jsAlone: ['src/assets/scripts/**/*.js', '!src/assets/scripts/{public,private}/**/*.js', `src/assets/scripts/private/${ENV_CONFIG.PRIVATE}/**/*.js`], // 独立编译
  html: ['src/views/**/*.html', '!src/views/{include,private}/**/*.html', `src/views/private/${ENV_CONFIG.PRIVATE}/**/*.html`]
}

// 代理
const proxyList = [
  createProxyMiddleware('/api/', {
    target: ENV_CONFIG.API, // 目标接口域名
    changeOrigin: true, // 如果我们代理的目标地址是一个虚拟托管站点，比如 http://www.baidu.com 这种域名的形式的target项，则需要配置此项。如果target项为ip值，比如http://127.0.0.1 则不用配置此项
    logLevel: 'debug',
    /* pathRewrite: {
      '^/api': '' // 重写接口
    } */
  })
]

// 运行-默认
gulp.task('default', gulp.series(
  clean,
  createRevCollector,
  libraryOutput,
  configMin, jsPublicMergeMin, jsAloneMin,
  jsonOutput, imageMin, styleMin, htmlBuildMin,
  relativePath, staticSuffix,
  gulp.parallel(server, watch)
))

/**
 * 清除发布目录
 */
function clean() {
  return gulp.src(['dist', src.revCollector], {
    allowEmpty: true
  }).pipe(plug.clean())
}

/**
 * 创建映射目录
 * @param {Function} done
 */
function createRevCollector(done) {
  fs.mkdir(src.revCollector, { recursive: true }, (err) => {
    if (err) {
      return console.error('静态资源映射目录创建出错: ', err)
    }
    done()
  })
}

/**
 * 文件输出至目标目录
 * @param {Function} done
 */
function libraryOutput(done) {
  const dir = 'library'
  const fileNames = {}
  gulp.src(src.library)
    .pipe(gulp.dest(`dist/${dir}`))
    .on('end', () => {
      done()
    })
}

/**
 * 输出 json
 * @param {Function} done
 */
function jsonOutput(done) {
  const dir = 'jsons'
  const fileNames = {}
  const gulpPipe = gulp.src(src.json).pipe(plug.rev())
    .pipe(gulp.dest(`dist/${dir}`))
    .pipe(plug.rev.manifest(`${dir}.json`))
    .pipe(plug.jsonEditor((json) => {
      return utilSetPathNamePrefix(dir, json)
    }))
    .pipe(gulp.dest(src.revCollector))
    .on('end', () => {
      done()
    })
}

/**
 * 图片
 * @param {Function} done
 */
function imageMin(done) {
  const dir = 'images'
  const fileNames = {}
  gulp.src(src.image)
    .pipe(plug.rev())
    .pipe(gulp.dest(`dist/${dir}`))
    .pipe(plug.rev.manifest(`${dir}.json`))
    .pipe(plug.jsonEditor((json) => {
      return utilSetPathNamePrefix(dir, json)
    }))
    .pipe(gulp.dest(src.revCollector))
    .on('end', () => {
      done()
    })
}

/**
 * 编译压缩 less 映射源代码使控制台可定位到代码行
 * @param {Function} done
 */
function styleMin(done) {
  const dir = 'styles'
  const fileNames = {}
  const gulpPipe = gulp.src(src.style)
    .pipe(plug.sourcemaps.init()) // 源代码映射
    .pipe(plug.less())
  if (ENV === 'dev') {
    gulpPipe.pipe(plug.sourcemaps.write())
      .pipe(plug.rev())
      .pipe(gulp.dest(`dist/${dir}`))
      .pipe(plug.rev.manifest(`${dir}.json`))
      .pipe(plug.jsonEditor((json) => {
        return utilSetPathNamePrefix(dir, json)
      }))
      .pipe(gulp.dest(src.revCollector))
  } else {
    gulpPipe.pipe(plug.minifyCss())
      .pipe(plug.rev())
      .pipe(gulp.dest(`dist/${dir}`))
      .pipe(plug.rev.manifest(`${dir}.json`))
      .pipe(plug.jsonEditor((json) => {
        return utilSetPathNamePrefix(dir, json)
      }))
      .pipe(gulp.dest(src.revCollector))
  }
  gulpPipe.on('end', () => {
    done()
  })
}

/**
 * html编译压缩
 * @param {Function} done
 */
function htmlBuildMin(done) {
  let option = {
    removeComments: true, // 清除HTML注释
    collapseWhitespace: true, // 压缩HTML
    collapseBooleanAttributes: true, // 省略布尔属性的值 <input checked="true"/> ==> <input checked />
    removeEmptyAttributes: true, // 删除所有空格作属性值 <input id="" /> ==> <input />
    removeScriptTypeAttributes: true, // 删除<script>的type="text/javascript"
    removeStyleLinkTypeAttributes: true, // 删除<style>和<link>的type="text/css"
    minifyJS: true, // 压缩页面JS
    minifyCSS: true // 压缩页面CSS
  }
  let gulpPipe = gulp.src(src.html)
    .pipe(plug.template({ theme: ENV_CONFIG.THEME }))
    .pipe(plug.fileInclude({
      prefix: '@',
      basepath: './src/views/include',
      indent: true
    }))
  // 非开发环境压缩文件
  if (ENV !== 'dev') {
    gulpPipe.pipe(plug.htmlmin(option))
  }
  gulpPipe.pipe(gulp.dest('dist/views')).on('end', () => {
    done()
  })
}

/**
 * 环境配置注入
 * @param {Function} done
 */
function configMin(done) {
  const dir = 'scripts'
  const fileName = 'env-config.js'
  const envConfig = { ...ENV_CONFIG }
  // 开发环境 并且开启代理时 接口地址前缀清空
  if (ENV === 'dev' && packageJson.serve.proxy) {
    envConfig.API = ''
  }
  gulp.src(fileName)
    .pipe(plug.template({ config: JSON.stringify(envConfig) }))
    .pipe(gulp.dest(`dist/${dir}`))
    .on('end', () => {
      done()
    })
}

/**
 * 公共js合并压缩
 * @param {Function} done
 */
function jsPublicMergeMin(done) {
  const dir = 'scripts'
  const fileName = 'public.js'
  const gulpPipe = gulp.src(src.jsPublic)
    .pipe(plug.plumber())
    .pipe(plug.eslint())
    .pipe(plug.eslint.format())
    .pipe(plug.eslint.failAfterError())
    .pipe(plug.concat(fileName))
    .pipe(plug.babel({
      'presets': ['env']
    }))
  if (ENV === 'dev') {
    gulpPipe.pipe(plug.rev())
      .pipe(gulp.dest(`dist/${dir}`))
      .pipe(plug.rev.manifest('js-public.json'))
      .pipe(plug.jsonEditor((json) => {
        return utilSetPathNamePrefix(dir, json)
      }))
      .pipe(gulp.dest(src.revCollector))
  } else {
    gulpPipe.pipe(plug.uglify())
      .pipe(gulp.dest(`dist/${dir}`))
  }
  gulpPipe.on('end', () => {
    done()
  })
}

/**
 * 独立js压缩
 * @param {Function} done
 */
function jsAloneMin(done) {
  const dir = 'scripts'
  const fileNames = {}
  const gulpPipe = gulp.src(src.jsAlone)
    .pipe(plug.plumber())
    // 语法检测
    .pipe(plug.eslint())
    .pipe(plug.eslint.format())
    .pipe(plug.eslint.failAfterError())
    // es6+ 转换es5
    .pipe(plug.babel({
      'presets': ['env']
    }))
  if (ENV === 'dev') {
    gulpPipe.pipe(plug.rev())
      .pipe(gulp.dest(`dist/${dir}`))
      .pipe(plug.rev.manifest('js-alone.json'))
      .pipe(plug.jsonEditor((json) => {
        return utilSetPathNamePrefix(dir, json)
      }))
      .pipe(gulp.dest(src.revCollector))
  } else {
    gulpPipe.pipe(plug.uglify())
      .pipe(plug.rev())
      .pipe(gulp.dest(`dist/${dir}`))
      .pipe(plug.rev.manifest('js-alone.json'))
      .pipe(plug.jsonEditor((json) => {
        return utilSetPathNamePrefix(dir, json)
      }))
      .pipe(gulp.dest(src.revCollector))
  }
  gulpPipe.on('end', () => {
    done()
  })
}

/**
 * 开发环境 || 测试环境 修改静态资源路径为相对路径
 * 其他环境 修改静态资源路径为cdn路径
 * @param {Object} done
 */
function relativePath(done) {
  gulp.src('dist/{views,scripts,styles}/**/*')
    .pipe(plug.replace(/@\/(views|jsons|styles|images|scripts|library)\//g, function (match) {
      let fileDir = this.file.dirname.replace(__dirname, '')
      let matchDir = path.normalize(`/dist${match.substr(1)}`)
      let dir = path.relative(fileDir, matchDir).replace(/\\/g, '/')
      return `${dir ? dir : '.'}/`
    }))
    .pipe(gulp.dest('dist')).on('end', () => {
      done()
    })
}

/**
 * 修改静态资源地址为映射地址
 * @param {Function} done
 */
function staticSuffix(done) {
  gulp.src([src.revCollector + '/*.json', 'dist/{views,scripts,styles}/**/*'])
    .pipe(plug.revCollector())
    .pipe(gulp.dest('dist')).on('end', () => {
      done()
    })
}

/**
 * 启动本地服务
 * @param {Function} done
 */
function server(done) {
  if (ENV === 'dev') {
    browserSync({
      ui: false, // 禁用UI
      notify: false, // 不显示在浏览器中的任何通知
      logPrefix: packageJson.name, // 改变控制台日志前缀
      open: packageJson.serve.open, // 是否打开浏览器
      port: packageJson.serve.port, // 端口
      files: 'dist', // 监听文件，更改时页面刷新
      // 服务配置
      server: {
        baseDir: 'dist' // 目标文件夹
      },
      // 中间件
      middleware: [(req, res, next) => {
        if (req.url === '/') {
          // 默认路径，重定向到入口文件
          res.writeHead(302, {
            'Location': packageJson.serve.indexPage
          })
          res.end()
        }
        next()
      }, ...proxyList]
    })
  }
  done()
}

/**
 * 监听文件修改 在开发环境下动态编译
 * @param {Function} done
 */
function watch(done) {
  if (ENV === 'dev') {
    gulp.watch(src.library, gulp.series(libraryOutput, 'watchPublic'))
    gulp.watch(src.image, gulp.series(imageMin, 'watchPublic'))
    gulp.watch(src.json, gulp.series(jsonOutput, 'watchPublic'))
    gulp.watch(src.style, gulp.series(styleMin, 'watchPublic'))
    gulp.watch('src/views/**/*.html', gulp.series('watchPublic'))
    gulp.watch(src.jsPublic, gulp.series(jsPublicMergeMin, 'watchPublic'))
    gulp.watch(src.jsAlone, gulp.series(jsAloneMin, 'watchPublic'))
  }
  done()
}
gulp.task('watchPublic', gulp.series(htmlBuildMin, relativePath, staticSuffix))

/**
 * 工具方法-输出资源映射JSON文件
 * @param {Object} file 文件
 * @param {String} dir 用于拆分的地址
 */
const utilGetPathName = (file, dir) => {
  const pathStr = file.path.split(dir)[1]
  const pathName = pathStr.replace(/\\/g,"/").substr(1, pathStr.length).replace(`private/${ENV_CONFIG.PRIVATE}/`, '')
  return pathName
}

/**
 * 工具方法-修改资源映射JSON文件前缀
 * @param {String} dir 用于拆分的地址
 * @param {Object} json 数据
 */
const utilSetPathNamePrefix = (dir, json) => {
  const newJson = {}
  for (const key in json) {
    newJson[`${dir}/${key}`] = `${dir}/${json[key]}`
  }
  return newJson
}